package com.easy.secret.codec;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

/**
 * @ProjectName: esay-secret-server
 * @Package: com.easy.secret.codec
 * @ClassName: AESCodec
 * @Description: java类作用描述
 * @Author: wangjc
 * @CreateDate: 2023/11/17
 * @UpdateUser: wangjc
 * @UpdateDate: 2023/11/17
 * @UpdateRemark: 更新内容
 * @Version: 1.0
 */
public class AESCodec {
    public static final String MODE = "AES/CBC/PKCS5PADDING";
    public static final String RANDOM_STR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&";
    private IvParameterSpec ivParameterSpec;
    private SecretKeySpec secretKeySpec;
    private Cipher cipher;
    private String aesToStringType = "base64";

    public AESCodec() {
    }

    public static AESCodec instance() {
        return new AESCodec();
    }

    public static AESCodec instance(String secretKeySpec, String ivParameterSpec, String mode, String aesToStringType) throws Exception {
        AESCodec codec = new AESCodec();
        if (StringUtils.isNotBlank(ivParameterSpec)) {
            codec.ivParameterSpec = new IvParameterSpec(ivParameterSpec.getBytes("UTF-8"));
        }

        codec.secretKeySpec = new SecretKeySpec(secretKeySpec.getBytes("UTF-8"), "AES");
        if (StringUtils.isNotBlank(mode)) {
            codec.cipher = Cipher.getInstance(mode);
        } else {
            codec.cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        }

        if (StringUtils.isNotBlank(aesToStringType)) {
            codec.aesToStringType = aesToStringType;
        }

        return codec;
    }

    public static AESCodec instance(byte[] secretKeySpec, byte[] ivParameterSpec, String aesToStringType) throws Exception {
        return instance(secretKeySpec, ivParameterSpec, "AES/CBC/PKCS5PADDING", aesToStringType);
    }

    public static AESCodec instance(byte[] secretKeySpec, byte[] ivParameterSpec, String mode, String aesToStringType) throws Exception {
        AESCodec codec = new AESCodec();
        if (ivParameterSpec != null) {
            codec.ivParameterSpec = new IvParameterSpec(ivParameterSpec);
        }

        codec.secretKeySpec = new SecretKeySpec(secretKeySpec, "AES");
        if (StringUtils.isNotBlank(mode)) {
            codec.cipher = Cipher.getInstance(mode);
        } else {
            codec.cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        }

        if (StringUtils.isNotBlank(aesToStringType)) {
            codec.aesToStringType = aesToStringType;
        }

        return codec;
    }

    public static AESCodec instanceBase64(String secretKeySpec, String ivParameterSpec, String aesToStringType) throws Exception {
        return instanceBase64(secretKeySpec, ivParameterSpec, "AES/CBC/PKCS5PADDING", aesToStringType);
    }

    public static AESCodec instanceBase64(String secretKeySpec, String ivParameterSpec, String mode, String aesToStringType) throws Exception {
        AESCodec codec = new AESCodec();
        Base64Codec base64Codec = Base64Codec.instance();
        if (StringUtils.isNotBlank(ivParameterSpec)) {
            codec.ivParameterSpec = new IvParameterSpec(base64Codec.decodeByte(ivParameterSpec, (String)null));
        }

        codec.secretKeySpec = new SecretKeySpec(base64Codec.decodeByte(secretKeySpec, (String)null), "AES");
        if (StringUtils.isNotBlank(mode)) {
            codec.cipher = Cipher.getInstance(mode);
        } else {
            codec.cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        }

        if (StringUtils.isNotBlank(aesToStringType)) {
            codec.aesToStringType = aesToStringType;
        }

        return codec;
    }

    public String encode(String str) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        return this.encode(str, (String)null);
    }

    public String encode(String original, String charset) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        if (StringUtils.isBlank(original)) {
            return null;
        } else {
            if (StringUtils.isBlank(charset)) {
                charset = "UTF-8";
            }

            if (this.ivParameterSpec != null) {
                this.cipher.init(1, this.secretKeySpec, this.ivParameterSpec);
            } else {
                this.cipher.init(1, this.secretKeySpec);
            }

            byte[] encrypted = this.cipher.doFinal(original.getBytes(charset));
            if ("hex".equalsIgnoreCase(this.aesToStringType)) {
                return ByteUtils.toHexString(encrypted);
            } else if ("base64".equalsIgnoreCase(this.aesToStringType)) {
                return Base64.encodeBase64String(encrypted);
            } else {
                throw new RuntimeException("encrypt error");
            }
        }
    }

    public String encodeByte(byte[] original, String charset) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        if (StringUtils.isBlank(charset)) {
            charset = "UTF-8";
        }

        if (this.ivParameterSpec != null) {
            this.cipher.init(1, this.secretKeySpec, this.ivParameterSpec);
        } else {
            this.cipher.init(1, this.secretKeySpec);
        }

        byte[] encrypted = this.cipher.doFinal(original);
        if ("hex".equalsIgnoreCase(this.aesToStringType)) {
            return ByteUtils.toHexString(encrypted);
        } else if ("base64".equalsIgnoreCase(this.aesToStringType)) {
            return Base64.encodeBase64String(encrypted);
        } else {
            throw new RuntimeException("encrypt error");
        }
    }

    public byte[] encodeByte(byte[] original) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        if (this.ivParameterSpec != null) {
            this.cipher.init(1, this.secretKeySpec, this.ivParameterSpec);
        } else {
            this.cipher.init(1, this.secretKeySpec);
        }

        return this.cipher.doFinal(original);
    }

    public String decode(String encodeStr) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        return this.decode(encodeStr, (String)null);
    }

    public String decode(String encodeStr, String charset) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        if (StringUtils.isBlank(encodeStr)) {
            return null;
        } else {
            if (StringUtils.isBlank(charset)) {
                charset = "UTF-8";
            }

            if (this.ivParameterSpec != null) {
                this.cipher.init(2, this.secretKeySpec, this.ivParameterSpec);
            } else {
                this.cipher.init(2, this.secretKeySpec);
            }

            byte[] decryptedBytes = null;
            if ("hex".equalsIgnoreCase(this.aesToStringType)) {
                decryptedBytes = this.cipher.doFinal(ByteUtils.fromHexString(encodeStr));
            } else {
                if (!"base64".equalsIgnoreCase(this.aesToStringType)) {
                    throw new RuntimeException("encrypt error");
                }

                decryptedBytes = this.cipher.doFinal(Base64.decodeBase64(encodeStr));
            }

            return new String(decryptedBytes, charset);
        }
    }

    public String decodeByte(byte[] base64, String charset) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        if (StringUtils.isBlank(charset)) {
            charset = "UTF-8";
        }

        if (this.ivParameterSpec != null) {
            this.cipher.init(2, this.secretKeySpec, this.ivParameterSpec);
        } else {
            this.cipher.init(2, this.secretKeySpec);
        }

        byte[] decryptedBytes = this.cipher.doFinal(base64);
        return new String(decryptedBytes, charset);
    }

    public byte[] decodeByte(byte[] base64) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        if (this.ivParameterSpec != null) {
            this.cipher.init(2, this.secretKeySpec, this.ivParameterSpec);
        } else {
            this.cipher.init(2, this.secretKeySpec);
        }

        return this.cipher.doFinal(base64);
    }

    public String generateKey() {
        return RandomStringUtils.random(16, RANDOM_STR);
    }

    public String getIvParameter() {
        return RandomStringUtils.random(16, RANDOM_STR);
    }
}
